#include "../include/fsext.h"
#include "../include/buffer.h"
#include "../include/superblock.h"
#include "../include/inode.h"
#include "../include/ptrlist.h"
#include "../include/ptrarray.h"
#include "../include/file.h"
#include "../include/state.h"
#include "../include/winapi.h"
#include "../include/macro_disk.h"
#include "../include/macro_report.h"
#include "../include/macro_constant.h"
#include "../include/macro_str.h"
#include "../include/macro_file_type.h"
#include "../include/macro_report.h"
#include "../include/exception.h"

FsExt::FsExt(){}
FsExt::~FsExt(){}
FsExt::FsExt(uint32 start_sector,SuperBlock& super_block,uint8 type,HANDLE disk_handle):FS(start_sector,type,disk_handle)
{
	block_size=super_block.blockSize();
	inode_size=super_block.inodeSize();
	inodes_per_group=super_block.inodesPerGroup();
	group_desc_start=super_block.groupDescStart();
	desc_size=super_block.descSize();
	desc_count_per_block=super_block.descsPerBlock();
	total_block_count_low=super_block.totalBlockCountLow();
	total_block_count_high=super_block.totalBlockCountHigh();
	fs_name=super_block.fsName();
}


bool FsExt::copy(const wint16* from_path,const wint16* to_path) const
{
	Inode inode=findFile(from_path);
	if(inode.isNull())
	{
		State::state().reportError(ERROR_TYPE_FIlE_NOT_FOUND,0);
		return false;
	}
	return copy(inode,to_path);
}
bool FsExt::copy(uint32 inode_num,const wint16* to_path) const
{
	return copy(readInode(inode_num),to_path);
}
bool FsExt::copy(const Inode& inode,const wint16* to_path) const
{
	try
	{
		State::state().initState();
		switch(inode.fileType())
		{
			case INODE_FILE_TYPE_NORMAL:
			{
				uint64 file_size=inode.fileSize();
				uint32 file_block_count=file_size/block_size;
				if((file_size!=0&&file_size<block_size)||file_size%block_size!=0)
					file_block_count++;
				State::state().reportBlockCount(PROG_TOTAL_BLOCK_COUNT,file_block_count);
				copyFile(inode,to_path);
				break;
			}
			case INODE_FILE_TYPE_DIR:
			{
				uint32 file_block_count=0;
				PtrArray<File> files=scanDirWithRecursion(inode,&file_block_count);
				State::state().reportBlockCount(PROG_TOTAL_BLOCK_COUNT,file_block_count);
				copyDir(files,to_path); 
				break;
			}
			default: 
				;
		}
	}
	catch(ExpCancel& e)
	{
		State::state().reportError(e.type,0);
		return false;
	}
	catch(ExpWinAPI& e)
	{
		State::state().reportError(e.type,e.code);
		return false;
	}
	return true;
}
void FsExt::copyDir(PtrArray<File>& files,const wint16* file_path) const
{
	WinAPI::creatDir(file_path);
	uint32 path_length=0;
	while(*(file_path+path_length)!=0)
		path_length++; 
	for(uint32 i=FILE_START_INDEX;i<files.size();i++)
	{
		//****************************************************************************************************
		uint32 j=0;
		Buffer path(path_length*2+files[i].dir_entry.file_name.size()+2+2);
		for(;j<path_length;j++)
			*((wint16*)path.data()+j)=*(file_path+j);
		*((wint16*)path.data()+j)=BACKSLASH; 
		for(j=0;j<files[i].dir_entry.file_name.size();j++)
			*(path.data()+(path_length+1)*2+j)=*(files[i].dir_entry.file_name.data()+j);
		*(wint16*)(path.data()+path.size()-2)=0;
		//****************************************************************************************************
		switch(files[i].inode.fileType())
		{
		case INODE_FILE_TYPE_NORMAL:
			State::state().reportFileName(PROG_FILE_NAME,files[i].name(),files[i].nameSize());
			copyFile(files[i].inode,(wint16*)path.data());
			break;
		case INODE_FILE_TYPE_DIR:
			copyDir(files[i].sub_dir,(wint16*)path.data());  
			break;
		default:
			;
		}
	}
}
void FsExt::copyFile(const Inode& inode,const wint16* file_path) const
{
	HANDLE hfile=NULL;
	ParaForCopyFile* para=NULL;
	try
	{
		hfile=WinAPI::createFile(file_path);
		uint32 file_block_count=inode.fileSize()/block_size+1;
		para=new ParaForCopyFile(&inode,hfile,file_block_count,&FsExt::copyFileInBlocks);
		if(inode.isUseExtent())
			handle4(para);
		else
			handle3(para);
	}
	catch(ExpCancel& e)
	{
		WinAPI::closeHandle(hfile);
		hfile=NULL;
		if(e.is_del_file)
			WinAPI::deleteFile(file_path);
		if(e.is_throw_up)
		{
			delete para;
			throw;
		}
	}
	catch(ExpWinAPI& e)
	{
		WinAPI::closeHandle(hfile);
		hfile=NULL;
		if(e.is_del_file)
			WinAPI::deleteFile(file_path);
		delete para;
		throw;
	}
	delete para;
	WinAPI::closeHandle(hfile);
}
PtrArray<File> FsExt::scanDirWithRecursion(const Inode& inode,uint32* total_file_size) const
{
	PtrList<File> files(8,false);
	ParaForScanDir* para=new ParaForScanDirWithRecursion(&inode,&files,total_file_size,&FsExt::scanDirWithRecursionInBlocks);
	try
	{
		if(inode.isUseExtent())
			handle4(para);
		else
			handle3(para);
	}
	catch(ExpCancel& e)
	{
		State::state().reportError(e.type,0);
		delete para;
		return PtrArray<File>();
	}
	catch(ExpWinAPI& e)
	{
		State::state().reportError(e.type,e.code);
		delete para;
		return PtrArray<File>();
	}
	delete para;
	PtrArray<File> tmp(files.size);
	files.copyTo(tmp);
	return tmp;
}


PtrArray<File> FsExt::scanDir(const wint16* path) const
{
	Inode inode=findFile(path);
	if(inode.isNull())
	{
		State::state().reportError(ERROR_TYPE_FIlE_NOT_FOUND,0);
		return PtrArray<File>();
	}
	return scanDir(inode);
}
PtrArray<File> FsExt::scanDir(uint32 inode_num) const
{
	return scanDir(readInode(inode_num));
}
PtrArray<File> FsExt::scanDir(const Inode& inode) const
{
	PtrList<File> files(8,false);
	ParaForScanDir* para=new ParaForScanDir(&inode,&files,&FsExt::scanDirInBlocks);
	try
	{
		if(inode.isUseExtent())
			handle4(para);
		else
			handle3(para);
	}
	catch(ExpCancel& e)
	{
		State::state().reportError(e.type,0);
		delete para;
		return PtrArray<File>();
	}
	catch(ExpWinAPI& e)
	{
		State::state().reportError(e.type,e.code);
		delete para;
		return PtrArray<File>();
	}
	delete para;
	PtrArray<File> tmp(files.size);
	files.copyTo(tmp);
	return tmp;
}


bool FsExt::checkDir(uint32 inode_num) const
{
	Inode inode=readInode(inode_num);
	return checkDir(readInode(inode_num));
}
bool FsExt::checkDir(const wint16* path) const
{
	Inode inode=findFile(path);
	if(inode.isNull())
	{
		State::state().reportError(ERROR_TYPE_FIlE_NOT_FOUND,0);
		return false;
	}
	return checkDir(inode);
}
bool FsExt::checkDir(const Inode& inode) const
{
	ParaForCheckDir* para=new ParaForCheckDir(&inode,&FsExt::checkDirInBlocks);
	try
	{
		if(inode.isUseExtent())
			handle4(para);
		else
			handle3(para);
	}
	catch(bool& e)
	{
		delete para;
		return e;
	}
	catch(ExpCancel& e)
	{
		State::state().reportError(e.type,0);
		delete para;
		return false;
	}
	catch(ExpWinAPI& e)
	{
		State::state().reportError(e.type,e.code);
		delete para;
		return false;
	}
	delete para;
	return false;
}


const Inode FsExt::findFile(const wint16* file_path) const
{
	Inode inode;
	const wint16* walker=file_path;
	uint32 length=0;
	if(*file_path!=SLASH)
		return inode;
	inode=readInode(ROOT_INODE_NUM);
	ParaForFindFile* para=new ParaForFindFile(&inode,NULL,&FsExt::findFileInBlocks);
	while(*walker!=0)
	{
		length=0;
		walker++;
		while(*(walker+length)!=SLASH&&*(walker+length)!=0)
			length++;
		if(length==0)
			break;
		Buffer file_name((uint8*)(walker),length*2);
		walker+=length;
		para->file_name=&file_name;
		try
		{
			if(inode.isUseExtent())
				handle4(para);
			else
				handle3(para);
		}
		catch(uint32 inode_num)
		{
			inode=readInode(inode_num);
			continue;
		}
		delete para;
		return Inode();
	}
	delete para;
	return inode;
}

bool FsExt::isBlockValid(uint64 block_num) const
{
	uint64 block_count=total_block_count_high;
	block_count=(block_count<<32)+total_block_count_low;
	return (block_num!=0&&block_num<block_count)?true:false;
}
void FsExt::groupBlockNum(const uint32* start_block_num,std::vector<const uint32*>& block_nums,uint32 block_count) const
{
	const uint32* tail=start_block_num+block_count;
	const uint32* walker=start_block_num;
	uint32 i=0;
	block_nums.push_back(start_block_num);
	while((walker+i)<tail-1)
	{
		if(*(walker+i)+1!=*(walker+i+1))
		{
			block_nums.push_back(walker+i+1);
			walker=walker+i+1;
			i=0;
			continue;
		}
		i++;
	}
	block_nums.push_back(tail);
}
void FsExt::readBlocks(uint64 start_block_num,Buffer& buffer,uint32 block_count) const
{
	DWORD number_of_read=0;
	uint32 sector_pointer=start_sector+((start_block_num*block_size)>>SECTOR_SIZE_POWR1);
	WinAPI::readData(disk_handle,sector_pointer,block_count*block_size,buffer);
}
const Inode FsExt::readInode(uint32 inode_num) const
{
	Buffer buffer(block_size);
	uint32 group_count_inode=(inode_num - 1) / inodes_per_group;
	uint32 inode_offset = (inode_num - 1) % inodes_per_group;
	uint32 block_count_group_descor = group_count_inode / desc_count_per_block;
	uint32 group_descor_offset_block = group_count_inode % desc_count_per_block;
	readBlocks(group_desc_start + block_count_group_descor,buffer,1);
	uint32 inode_table = *(uint32*)(buffer.data() + (group_descor_offset_block * desc_size) + 0x08);
	uint32 inode_count_inode = inode_offset / (block_size / inode_size);
	uint32 inode_offset_block = inode_offset % (block_size / inode_size);
	uint32 inode_offset_block_byte = (inode_offset_block * inode_size);
	readBlocks(inode_table + inode_count_inode,buffer,1);
	uint8* inode_start=buffer.data()+inode_offset_block_byte;
	return Inode(inode_start);
}
















